using System;
using System.Collections.Generic;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

namespace mirror
{
    class Camera : Singleton<Camera>
    {
        private static Random random = new Random();
        public static Random Random
        {
            get { return random; }
        }

        public class CameraShake
        {
            public float magnitude;
            int duration;

            public CameraShake(float zMag, int zDuration)
            {
                magnitude = zMag;
                duration = zDuration;
            }

            public bool Update()
            {
                duration -= 1;
                if (duration < 0)
                    return false;
                return true;
            }
        }

        List<CameraShake> mShakes;
        Vector3 mShakeVector;

        Vector2 mScreenSize;

        Vector3 mPosition;
        bool mAct;
        Matrix mViewMatrix;
        Matrix mProjectionMatrix;
        Vector3 mTarget;

        float mLEFTRIGHTrotation;
        float mUPDOWNrotation;
        float mLEFTRIGHTrotationSpeed = 1.0f;
        float mUPDOWNrotationSpeed = 1.0f;

        public Camera()
        {

        }

        public Vector3 Position
        {
            get { return mPosition; }
            set { mPosition = value; }
        }

        public Matrix ProjectionMatrix
        {
            get { return mProjectionMatrix; }
            set { mProjectionMatrix = value; }
        }
        public Matrix ViewMatrix
        {
            get { return mViewMatrix; }
            set { mViewMatrix = value; }
        }

        public void Create(Vector2 screenSize, Vector2 rotationSpeeds)
        {
            mShakeVector = Vector3.Zero;
            mPosition = new Vector3(5, 5, 5);
            mAct = true;

            mProjectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, screenSize.X / screenSize.Y, 0.3f, 1000.0f);

            mScreenSize = screenSize;
            mLEFTRIGHTrotationSpeed = rotationSpeeds.X;
            mUPDOWNrotationSpeed = rotationSpeeds.Y;
            mShakes = new List<CameraShake>();
        }

        public void Update(float dt, Vector2 rotations, Vector3 position)
        {
            mPosition = position;
            mLEFTRIGHTrotation = rotations.X;
            mUPDOWNrotation = rotations.Y;

            UpdateViewMatrix();

            Mouse.SetPosition((int)mScreenSize.X / 2, (int)mScreenSize.Y / 2);
        }

        public void Update()
        {
            UpdateShake();
            if(mAct)
                UpdateControls();
            SetTarget(mTarget);
        }

        private void UpdateViewMatrix()
        {
            Matrix cameraRotation = Matrix.CreateRotationX(mUPDOWNrotation) * Matrix.CreateRotationY(mLEFTRIGHTrotation);

            Vector3 cameraOriginalTarget = new Vector3(0, 0, -1);
            Vector3 cameraOriginalUpVector = new Vector3(0, 1, 0);
            Vector3 cameraRotatedTarget = Vector3.Transform(cameraOriginalTarget, cameraRotation);
            Vector3 cameraFinalTarget = mPosition + cameraRotatedTarget;
            Vector3 cameraRotatedUpVector = Vector3.Transform(cameraOriginalUpVector, cameraRotation);

            mViewMatrix = Matrix.CreateLookAt(mPosition, cameraFinalTarget, cameraRotatedUpVector);
        }

        public void SetTarget(Vector3 zTarget)
        {
            mTarget = zTarget;
            float aspect = mScreenSize.X / mScreenSize.Y;
            mViewMatrix = Matrix.CreateLookAt(mPosition + mShakeVector, zTarget, Vector3.Up);
            mProjectionMatrix = Matrix.CreatePerspectiveFieldOfView(1, aspect, 1, 1000);
        }

        public Vector3 GetLook()
        {
            Vector3 newVec = mTarget - mPosition;
            newVec.Normalize();
            return newVec;
        }

        public Vector3 GetRight()
        {
            Vector3 look = mTarget - mPosition;
            look.Normalize();
            Vector3 right = Vector3.Cross(look, Vector3.Up);
            right.Normalize();
            return right;
        }

        public void UpdateControls()
        {
            float movement = 0.6f;

            Vector3 look = mTarget - mPosition;
            look.Normalize();
            look *= movement;
            Vector3 right = Vector3.Cross(look, Vector3.Up);
            right.Normalize();
            right *= movement;

            if (Keyboard.GetState().IsKeyDown(Keys.Q))
            {
                mPosition.Y +=  movement;
                mTarget.Y += movement;
            }
            if (Keyboard.GetState().IsKeyDown(Keys.E))
            {
                mPosition.Y -= movement;
                mTarget.Y -= movement;
            }
            if (Keyboard.GetState().IsKeyDown(Keys.W))
                mPosition += look;
            if (Keyboard.GetState().IsKeyDown(Keys.S))
                mPosition -= look;
            if (Keyboard.GetState().IsKeyDown(Keys.A))
                mPosition -= right;
            if (Keyboard.GetState().IsKeyDown(Keys.D))
                mPosition += right;
        }

        public void UpdateShake()
        {
            Vector3 totalShake = Vector3.Zero;
            for(int i = 0; i < mShakes.Count; i++)
            {
                if (!mShakes[i].Update())
                    mShakes.RemoveAt(i);
                else
                {
                    float mag = mShakes[i].magnitude;
                    //add some shake vector

                    float xrand = (float)(random.Next(-100, 100)) * mag / 100.0f;
                    float yrand = (float)(random.Next(-100, 100)) * mag / 100.0f;
                    float zrand = (float)(random.Next(-100, 100)) * mag / 100.0f;

                    totalShake += new Vector3(xrand, yrand, zrand);
                }
            }
            mShakeVector = totalShake;
        }


        public void Shake(float magnitude, int duration)
        {
            mShakes.Add(new CameraShake(magnitude, duration));
        }
    }
}
